package com.yanti.runner;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import com.yanti.common.constant.RedisConstants;
import com.yanti.domain.entity.Permission;
import com.yanti.domain.entity.Role;
import com.yanti.domain.entity.RolePermissionRel;
import com.yanti.mapper.PermissionMapper;
import com.yanti.mapper.RoleMapper;
import com.yanti.mapper.RolePermissionRelMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@Slf4j
public class PushRolePermissions2RedisRunner implements ApplicationRunner {

    @Resource
    private RoleMapper roleMapper;

    @Resource
    private RolePermissionRelMapper rolePermissionRelMapper;

    @Resource
    private PermissionMapper permissionMapper;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    // 权限同步标记 Key
    private static final String PUSH_PERMISSION_FLAG = "push.permission.flag";

    @Override
    public void run(ApplicationArguments args) {
        log.info("==> 服务启动，开始同步角色权限数据到 Redis 中...");
        try{
            // 是否能够同步数据: 原子操作，只有在键 PUSH_PERMISSION_FLAG 不存在时，才会设置该键的值为 "1"，并设置过期时间为 1 天
            boolean canPushed = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(PUSH_PERMISSION_FLAG, "1", 1, TimeUnit.DAYS));
            // 如果无法同步权限数据
            if (!canPushed) {
                log.warn("==> 角色权限数据已经同步至 Redis 中，不再同步...");
                return;
            }
            // 查询出所有的角色
            List<Role> roles = roleMapper.selectEnabledList();
            // 角色不为空
            if(CollUtil.isNotEmpty(roles)){
                // 查询出所有的角色ID
                List<Long> roleIds = roles.stream().map(Role::getId).toList();
                // 根据角色 ID 查询出角色权限关系
                List<RolePermissionRel> rolePermissionRels = rolePermissionRelMapper.selectByRoleIds(roleIds);
                // 按角色ID分组，每个角色ID对应多个权限
                Map<Long, List<Long>> roleIdPermissionIdsMap = rolePermissionRels.stream()
                        .collect(Collectors.groupingBy(RolePermissionRel::getRoleId,
                                Collectors.mapping(RolePermissionRel::getPermissionId, Collectors.toList())));
                // 查询APP端所有被启用的权限
                List<Permission> permissions = permissionMapper.selectAppEnabledList();
                // 权限ID - 权限
                Map<Long, Permission> permissionIdMap = permissions.stream().collect(
                        Collectors.toMap(Permission::getId, permissionDO -> permissionDO)
                );
                // 组织角色ID - 权限关系
                HashMap<Long, List<Permission>> roleIdPermissionMap = Maps.newHashMap();
                // 遍历角色ID - 权限ID 关系
                roles.forEach(role -> {
                    // 获取角色ID
                    Long roleId = role.getId();
                    // 当前角色 ID 对应的权限 ID 集合
                    List<Long> permissionIds = roleIdPermissionIdsMap.get(roleId);
                    // 权限ID集合不为空
                    if(CollUtil.isNotEmpty(permissionIds)){
                        // 遍历权限ID集合
                        List<Permission> permissionList = permissionIds.stream()
                                .map(permissionIdMap::get)
                                .filter(Objects::nonNull)
                                .toList();
                        // 权限集合不为空
                        if(CollUtil.isNotEmpty(permissionList)){
                            // 组织角色ID - 权限关系
                            roleIdPermissionMap.put(roleId,permissionList);
                        }
                    }
                });
                // 同步到 Redis 中，方便后续网关鉴权使用
                if(CollUtil.isNotEmpty(roleIdPermissionMap)){
                    roleIdPermissionMap.forEach((roleId, permissionsList) -> {
                        // 组织key
                        String key = RedisConstants.buildRolePermissionsKey(roleId);
                        // 同步到 Redis 中
                        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(permissionsList));
                    });
                }
            }
            log.info("==> 服务启动，成功同步角色权限数据到 Redis 中...");
        }catch (Exception e){
            log.error("==> 服务启动，同步角色权限数据到 Redis 中失败，原因：{}", e.getMessage());
        }
    }
}